/***************************************************************************
 *   Copyright (C) 2007 by Grzegorz Latarowski, Maciej Gajewski            *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/


// Qt
#include <QDateTime>
#include <QSslCipher>
#include <QSslKey>
#include <QSslCertificate>

// gcommon
#include "gexception.h"

// local
#include "gnetclient.h"



// ===========================================================================
//                                 GNetClient
// ===========================================================================


// --------------------------- GNetClient::GNetClient() ----------------------
GNetClient::GNetClient( QObject * pParent ) : QObject( pParent )
{
	_timeout	= 5000; // 5 sec
	_requestId	= (int)QDateTime::currentDateTime().toTime_t();
	
	connect( &_socket, SIGNAL( stateChanged( QAbstractSocket::SocketState ) ),
			  this, SLOT( socketStateChanged( QAbstractSocket::SocketState ) ) );
}
// ---------------------------------------------------------------------------


// --------------------- GNetClient::socketStateChanged() --------------------
void GNetClient::socketStateChanged( QAbstractSocket::SocketState socketState )
{
	switch( socketState )
	{
		case QAbstractSocket::UnconnectedState:
		{
			// emit that socket disconnectes
			emit disconnected();
		}
		break;
		
		default:
			break;
	}
}
// ---------------------------------------------------------------------------
	

// ----------------------- GNetClient::connectToHost() -----------------------
/// Connects to specified host synchronously. May throw GTimeoutError
void GNetClient::connectToHost( const QString & hostName, quint16 port )
{
// 	_socket.connectToHost( hostName, port );
// 	if ( ! _socket.waitForConnected( _timeout ) )
// 	{
// 		throw GTimeoutError( _socket.errorString() );
// 	}


	qDebug() << "\n-------------------- loading cert";
	QFile file2( "cprivkey.pem" );
	file2.open( QIODevice::ReadOnly );
	QSslKey key( &file2, QSsl::Rsa );
    qDebug() << "Key is null?" << key.isNull();
	_socket.setPrivateKey( key );

	QFile file( "ccacert.pem" );
    file.open( QIODevice::ReadOnly );
	QSslCertificate cert( &file );
    qDebug() << "Cert is null?" << cert.isNull();
	_socket.setLocalCertificate( cert );
	
	_socket.connectToHostEncrypted( hostName, port);
// 	_socket.ignoreSslErrors();
	
	if ( ! _socket.waitForEncrypted( 10000 ) )
	{
		qDebug() << "encryption failed" << _socket.error();
		QList<QSslError> errors = _socket.sslErrors();
		foreach( QSslError err, errors )
		{
			qDebug() << "sslError" << (int)(err.error()) << err;
		}
		throw GTimeoutError( _socket.errorString() );
	}
	else
	{
		qDebug() << "encrypted";
	}
}
// ---------------------------------------------------------------------------


// -------------------- GNetClient::disconnectFromHost() ---------------------
/// Disconects from host
void GNetClient::disconnectFromHost()
{
	_socket.disconnectFromHost();
	
	// if socket is already disconnected than skip waiting for disconnecting
	if( _socket.state() != QAbstractSocket::UnconnectedState )
	{
		if ( ! _socket.waitForDisconnected( _timeout ) )
		{
			throw GTimeoutError( _socket.errorString() );
		}
	}
}
// ---------------------------------------------------------------------------


// ----------------------- GNetClient::isConnected() -------------------------
bool GNetClient::isConnected()
{
	return _socket.state() == QAbstractSocket::ConnectedState;
}
// ---------------------------------------------------------------------------


// ----------------------- GNetClient::sendPacket() --------------------------
/// Sends packet
void GNetClient::sendPacket( const GPacket & packet )
{
	if ( isConnected() )
	{
		GHeader header = packet.header();

		header.writeToSocket( & _socket, _timeout );
		packet.writeToSocket( & _socket, _timeout );
	}
}

// ------------------ GNetClient::getRequestId() -----------------------------
/// Returns session-uniqe request id
quint32 GNetClient::getRequestId()
{
	_requestId++;
	return _requestId;
}
// ---------------------------------------------------------------------------


// ----------------- GNetClient::sendAuthorization() -------------------------
void GNetClient::sendAuthorization( const QString & login, const QString & password )
{
	if( !isConnected() ) return;
	
	GNetPacketAuthorization authorization;
	authorization.setLogin( login );
	authorization.setPassword( password );
	sendPacket( authorization );
	
	// wait for respones
	GNetPacket * pPacket = waitForPacket();

	GNetPacketAuthorizationAnswer * pAuthorizationAnswer;
	pAuthorizationAnswer = qobject_cast< GNetPacketAuthorizationAnswer * >(pPacket);

	if ( pAuthorizationAnswer )
	{
		
		if( pAuthorizationAnswer->status() == GNetPacketAuthorizationAnswer::STATUS_OK )
		{
				// simply quit on succesfull
			return;
		}
	}
		// process that authorization failed
	emit authorizationFailed( pAuthorizationAnswer->message() );
	disconnectFromHost();
	emit disconnected();
}
// ---------------------------------------------------------------------------


// ----------------------- GNetClient::waitForReply() -----------------------
GNetPacketReply * GNetClient::waitForReply( uint requestId )
{
	// TODO handling timeouts and other errors
	while( 1 )
	{
		// wait for any packet
		GNetPacket * pPacket = waitForPacket();

		// is this a reply?
		GNetPacketReply * pReply = qobject_cast< GNetPacketReply * >( pPacket );
		if ( pReply )
		{
			if ( pReply->requestId() == requestId )
			{
				return pReply;
			}
			else
			{
				qDebug("GNetClient::waitForReply: unexpected reply, request id=%d",
					   pReply->requestId()
					  );
			}
		}
		else
		{
			return NULL;
		}
		// ignore al other packets
	}
}
// ---------------------------------------------------------------------------


// ------------------------ GNetClient::waitForPacket() ---------------------
GNetPacket * GNetClient::waitForPacket()
{
	if ( isConnected() )
	{
		GHeader header;

			// read header
		header.readFromSocket( & _socket, _timeout );
			// create apropriate packet
		GNetPacket * pPacket = GNetPacketFactory::instance()->createPacket( header );
			// read packet
		if ( pPacket )
		{
			pPacket->readFromSocket( & _socket, _timeout );
			return pPacket;
		}
		
	}

	return NULL;
}
// ---------------------------------------------------------------------------

// eof
